package com.hmall.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.domain.PageDTO;
import com.hmall.search.domain.po.Item;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.mapper.SearchMapper;
import com.hmall.search.service.ISearchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class SearchServiceImpl extends ServiceImpl<SearchMapper, Item> implements ISearchService {
    
    private final ElasticsearchClient esClient;
    
    
    /**
     * 列表分页,使用es
     * 支持:
     * 1 输入关键字进行全文搜索 匹配商品名称,品牌,分类信息
     * 2 按分类,品牌,价格进行检索
     * 3 商品名称高亮显示
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<ItemDoc> listPage(ItemPageQuery query) throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        /*.query(q ->
                                q.multiMatch(m ->
                                        m.fields("name", "brand", "category")
                                                .query(query.getKey())
                                )
                        
                        )*/
                        // 按分类,品牌,价格进行检索
                        //全文检索
                        .trackTotalHits(t -> t.enabled(true))
                        .query(q ->
                                q.bool(b -> {
                                            // 必须的全文检索
                                            b.must(m -> {
                                                if (!query.getKey().isBlank()) {
                                                    m.multiMatch(mm -> mm
                                                            .query(query.getKey())
                                                            .fields("name", "brand", "category", "spec"));
                                                } else {
                                                    //查询所有记录
                                                    m.matchAll(mm -> mm);
                                                }
                                                return m;
                                            });
                                            // 动态添加过滤条件
                                            if (query.getCategory() != null) {
                                                b.filter(f -> f.term(t ->
                                                        t.field("category").value(query.getCategory())
                                                ));
                                            }
                                            if (query.getBrand() != null) {
                                                b.filter(f -> f.term(t ->
                                                        t.field("brand").value(query.getBrand())
                                                ));
                                            }
                                            if (query.getMinPrice() != null || query.getMaxPrice() != null) {
                                                b.filter(f -> f.range(r -> {
                                                    RangeQuery.Builder range = new RangeQuery.Builder();
                                                    if (query.getMinPrice() != null) {
                                                        range.gte(JsonData.of(query.getMinPrice()));
                                                    }
                                                    if (query.getMaxPrice() != null) {
                                                        range.lte(JsonData.of(query.getMaxPrice()));
                                                    }
                                                    return range.field("price");
                                                }));
                                            }
                                            return b;
                                        }
                                )
                        )
                        // 高亮
                        .highlight(h ->
                                h.fields("name", hf ->
                                        hf.preTags("<em>")
                                                .postTags("</em>")
                                )
                        )
                        .size(query.getPageSize())
                        .from((query.getPageNo() - 1) * query.getPageSize())
                
                , ItemDoc.class);
        // 组装响应结果
        long total = response.hits().total().value();
        List<Hit<ItemDoc>> hits = response.hits().hits();
        List<ItemDoc> list = new ArrayList<>(hits.size());
        
        hits.forEach(hit -> {
            ItemDoc source = hit.source();
            if (!hit.highlight().isEmpty()) {
                source.setName(hit.highlight().get("name").get(0));
            }
            
            list.add(source);
        });
        
        return new PageDTO<ItemDoc>(total, query.getPageSize().longValue(), list);
    }
    
    
    /**
     * 自动补全
     *
     * @param key
     * @return
     */
    @Override
    public List<String> suggestion(String key) throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(s -> s.index("items")
                        .suggest(sugg ->
                                sugg.text(key)
                                        .suggesters("suggestion_suggest",
                                                suggest ->
                                                        suggest.completion(c ->
                                                                c.field("suggestion")
                                                                        .skipDuplicates(true)
                                                                        .size(10)
                                                        )
                                        
                                        )
                        )
                
                , ItemDoc.class);
        List<Suggestion<ItemDoc>> suggestionList = response.suggest().get("suggestion_suggest");
        List<String> suggestions = new ArrayList<>();
        if (suggestionList != null && !suggestionList.isEmpty()) {
            suggestionList.stream().forEach(option -> {
                option.completion().options().forEach(item -> {
                    suggestions.add(item.text());
                });
            });
        }
        
        return suggestions;
    }
}
